/**********************************************************************

  Audacity: A Digital Audio Editor

  VSTEffectsModule.cpp

  Dominic Mazzoni

  Paul Licameli split from VSTEffect.cpp


*//********************************************************************/
#include "VSTEffectsModule.h"

#include "ModuleManager.h"
#include "wxArrayStringEx.h"

#if defined(__WXMSW__)
#include <Windows.h>
#include <shlwapi.h>
#endif

#include <wx/tokenzr.h>
#include <wx/utils.h>

// ============================================================================
//
// Module registration entry point
//
// This is the symbol that Audacity looks for when the module is built as a
// dynamic library.
//
// When the module is builtin to Audacity, we use the same function, but it is
// declared static so as not to clash with other builtin modules.
//
// ============================================================================
DECLARE_PROVIDER_ENTRY(AudacityModule)
{
    // Create our effects module and register
    // Trust the module manager not to leak this
    return std::make_unique<VSTEffectsModule>();
}

// ============================================================================
//
// Register this as a builtin module
//
// We also take advantage of the fact that wxModules are initialized before
// the wxApp::OnInit() method is called.  We check to see if Audacity was
// executed to scan a VST effect in a different process.
//
// ============================================================================
DECLARE_BUILTIN_PROVIDER(VSTBuiltin);

///////////////////////////////////////////////////////////////////////////////
///
/// Auto created at program start up, this initialises VST.
///
///////////////////////////////////////////////////////////////////////////////

VSTEffectsModule::VSTEffectsModule()
{
}

VSTEffectsModule::~VSTEffectsModule()
{
}

// ============================================================================
// ComponentInterface implementation
// ============================================================================

PluginPath VSTEffectsModule::GetPath() const
{
    return {};
}

ComponentInterfaceSymbol VSTEffectsModule::GetSymbol() const
{
    return XO("VST Effects");
}

VendorSymbol VSTEffectsModule::GetVendor() const
{
    return XO("The Audacity Team");
}

wxString VSTEffectsModule::GetVersion() const
{
    // This "may" be different if this were to be maintained as a separate DLL
    return AUDACITY_VERSION_STRING;
}

TranslatableString VSTEffectsModule::GetDescription() const
{
    return XO("Adds the ability to use VST effects in Audacity.");
}

// ============================================================================
// PluginProvider implementation
// ============================================================================

bool VSTEffectsModule::Initialize()
{
    // Nothing to do here
    return true;
}

void VSTEffectsModule::Terminate()
{
    // Nothing to do here
    return;
}

bool VSTEffectsModule::SupportsCustomModulePaths() const
{
    return true;
}

EffectFamilySymbol VSTEffectsModule::GetOptionalFamilySymbol()
{
    return VSTPLUGINTYPE;
}

const FileExtensions& VSTEffectsModule::GetFileExtensions()
{
    static FileExtensions result{ { _T("vst") } };
    return result;
}

FilePath VSTEffectsModule::InstallPath()
{
    // Not yet ready for VST drag-and-drop...
    // return FileNames::PlugInDir();

    return {};
}

void VSTEffectsModule::AutoRegisterPlugins(PluginManagerInterface&)
{
}

PluginPaths VSTEffectsModule::FindModulePaths(PluginManagerInterface& pm) const
{
    FilePaths pathList;
    FilePaths files;

    // Check for the VST_PATH environment variable
    wxString vstpath = wxString::FromUTF8(getenv("VST_PATH"));
    if (!vstpath.empty()) {
        wxStringTokenizer tok(vstpath, wxPATH_SEP);
        while (tok.HasMoreTokens())
        {
            pathList.push_back(tok.GetNextToken());
        }
    }

    const auto AddCustomPaths = [](PluginManagerInterface& pm, const VSTEffectsModule& module, FilePaths& pathList)
    {
        const auto customPaths = pm.ReadCustomPaths(module);
        std::copy(customPaths.begin(), customPaths.end(), std::back_inserter(pathList));
    };

#if defined(__WXMAC__)
#define VSTPATH wxT("/Library/Audio/Plug-Ins/VST")

    // Look in ~/Library/Audio/Plug-Ins/VST and /Library/Audio/Plug-Ins/VST
    pathList.push_back(wxGetHomeDir() + wxFILE_SEP_PATH + VSTPATH);
    pathList.push_back(VSTPATH);

    AddCustomPaths(pm, *this, pathList);

    // Recursively search all paths for Info.plist files.  This will identify all
    // bundles.
    pm.FindFilesInPathList(wxT("Info.plist"), pathList, files, true);

    // Remove the 'Contents/Info.plist' portion of the names
    for (size_t i = 0; i < files.size(); i++) {
        files[i] = wxPathOnly(wxPathOnly(files[i]));
        if (!files[i].EndsWith(wxT(".vst"))) {
            files.erase(files.begin() + i--);
        }
    }

#elif defined(__WXMSW__)

    TCHAR dpath[MAX_PATH];
    TCHAR tpath[MAX_PATH];
    DWORD len;

    // Try HKEY_CURRENT_USER registry key first
    len = WXSIZEOF(tpath);
    if (SHRegGetUSValue(wxT("Software\\VST"),
                        wxT("VSTPluginsPath"),
                        NULL,
                        tpath,
                        &len,
                        FALSE,
                        NULL,
                        0) == ERROR_SUCCESS) {
        tpath[len] = 0;
        dpath[0] = 0;
        ExpandEnvironmentStrings(tpath, dpath, WXSIZEOF(dpath));
        pathList.push_back(dpath);
    }

    // Then try HKEY_LOCAL_MACHINE registry key
    len = WXSIZEOF(tpath);
    if (SHRegGetUSValue(wxT("Software\\VST"),
                        wxT("VSTPluginsPath"),
                        NULL,
                        tpath,
                        &len,
                        TRUE,
                        NULL,
                        0) == ERROR_SUCCESS) {
        tpath[len] = 0;
        dpath[0] = 0;
        ExpandEnvironmentStrings(tpath, dpath, WXSIZEOF(dpath));
        pathList.push_back(dpath);
    }

    // Add the default path last
    dpath[0] = 0;
    ExpandEnvironmentStrings(wxT("%ProgramFiles%\\Steinberg\\VSTPlugins"),
                             dpath,
                             WXSIZEOF(dpath));
    pathList.push_back(dpath);

    dpath[0] = 0;
    ExpandEnvironmentStrings(wxT("%COMMONPROGRAMFILES%\\VST2"),
                             dpath,
                             WXSIZEOF(dpath));
    pathList.push_back(dpath);

    AddCustomPaths(pm, *this, pathList);

    // Recursively scan for all DLLs
    pm.FindFilesInPathList(wxT("*.dll"), pathList, files, true);

#else

    // Nothing specified in the VST_PATH environment variable...provide defaults
    if (vstpath.empty()) {
        // We add this "non-default" one
        pathList.push_back(wxT(LIBDIR) wxT("/vst"));

        // These are the defaults used by other hosts
        pathList.push_back(wxT("/usr/lib/vst"));
        pathList.push_back(wxT("/usr/local/lib/vst"));
        pathList.push_back(wxGetHomeDir() + wxFILE_SEP_PATH + wxT(".vst"));
    }

    AddCustomPaths(pm, *this, pathList);

    // Recursively scan for all shared objects
    pm.FindFilesInPathList(wxT("*.so"), pathList, files, true);

#endif

    return { files.begin(), files.end() };
}

unsigned VSTEffectsModule::DiscoverPluginsAtPath(
    const PluginPath& path, TranslatableString& errMsg,
    const RegistrationCallback& callback)
{
    VSTEffectBase effect(path);
    if (effect.InitializePlugin()) {
        auto effectIDs = effect.GetEffectIDs();
        if (effectIDs.empty()) {
            //Each VST plugin path in Audacity should have id(index) part in it
            effectIDs.push_back(0);
        }

        for (auto id : effectIDs) {
            //Subsequent VSTEffect::Load may seem like overhead, but we need
            //to initialize EffectDefinitionInterface part, which includes
            //properly formatted plugin path
            VSTEffectBase subeffect(wxString::Format("%s;%d", path, id));
            subeffect.Load();
            if (callback) {
                callback(this, &subeffect);
            }
        }
        return effectIDs.size();
    }
    errMsg = XO("Could not load the library");
    return 0;
}

std::unique_ptr<ComponentInterface>
VSTEffectsModule::LoadPlugin(const PluginPath& path)
{
    // Acquires a resource for the application.
    // For us, the ID is simply the path to the effect
    auto result = Factory::Call(path);
    if (!result->InitializePlugin()) {
        result.reset();
    }
    return result;
}

bool VSTEffectsModule::CheckPluginExist(const PluginPath& path) const
{
    const auto modulePath = path.BeforeFirst(wxT(';'));
    return wxFileName::FileExists(modulePath) || wxFileName::DirExists(modulePath);
}
